﻿using GACore.Structures;
using System;
using System.Collections.Generic;

namespace GACore.Helpers
{
	public class MarriageHelper
	{
		private MarriageProperties marriageProps;
		private Vector[] parents;
		private Random random = new Random();

		public MarriageHelper(MarriageProperties marriageProperties)
		{
			this.marriageProps = marriageProperties;
		}

		public Vector[] DoCrossover(Vector[] parents)
		{
			int length = parents.Length % 2 == 0 ? parents.Length / 2 : parents.Length / 2 + 1;
			this.parents = parents;
			int[][] marriageProfile = new int[2][];
			marriageProfile[0] = GetSelection(GetMarriage(marriageProps.SelectionSchema)(-1, marriageProps.CountOfSelectedVectors), length);
			marriageProfile[1] = GetMarriageVectors(marriageProfile[0], marriageProps.MarriageSchema);

			List<Vector> children = new List<Vector>();
			for (int i = 0; i < marriageProfile[0].Length; i++)
			{
				children.AddRange(parents[marriageProfile[0][i]].DoCrossover(parents[marriageProfile[1][i]]));
			}
			return children.ToArray();
		}

		private int[] GetMarriageVectors(int[] marriageProfile0, SelectionProfiles marriageProfile)
		{
			int[] ret = new int[marriageProfile0.Length];

			int currentStartIndex = 0;
			int current = 0;
			int count = 0;
			for (int i = 0; i < marriageProfile0.Length; i++)
			{
				if (marriageProfile0[i] != current)
				{
					if (count > 0)
					{
						int[] tmp = GetMarriage(marriageProfile)(current, count);
						Array.Copy(tmp, 0, ret, currentStartIndex, count);
					}

					currentStartIndex = i;
					current = marriageProfile0[i];
					count = 1;
				}
				else
				{
					count++;
				}
			}

			return ret;
		}

		private int[] GetSelection(int[] selectedVectors, int length)
		{
			int[] ret = new int[length];
			int current = length - 1;
			for (int i = 0; i < selectedVectors.Length; i++)
			{
				int currentCount = GetCountOfVector(length, selectedVectors.Length, i);
				for (int j = 0; j < currentCount; j++)
				{
					ret[current--] = selectedVectors[selectedVectors.Length - 1 - i];
				}
			}

			return ret;
		}

		private int GetCountOfVector(int length, int differentVectors, int currentVector)
		{
			return (int)(length / differentVectors * Math.Exp(currentVector / length));
		}

		private GetMarriageDelegate GetMarriage(SelectionProfiles profile)
		{
			switch (profile)
			{
				case SelectionProfiles.BestFitness:
					return GetMarriageBestFitness;
				case SelectionProfiles.Random:
					return GetMarriageRandom;
				case SelectionProfiles.DefinedMatrix:
					return GetMarriageDefinedMatrix;
				default:
					throw new ArgumentException("Bitte geben Sie eine Selektionsfunktion an.");
			}
		}

		private delegate int[] GetMarriageDelegate(int a0Value, int length);

		private int[] GetMarriageBestFitness(int a0Value, int length)
		{
			int[] ret = new int[length];
			for (int parentIndex = a0Value + 1, curIndex = 0; parentIndex <= a0Value + length; parentIndex++, curIndex++)
			{
				ret[curIndex] = parentIndex % parents.Length;
			}
			return ret;
		}

		private int[] GetMarriageRandom(int a0Value, int length)
		{
			int[] ret = new int[length];
			for (int i = 0; i < length; i++)
			{
				int r = a0Value;
				while (r == a0Value)
				{
					r = random.Next(parents.Length);
				}
				ret[i] = r;
			}
			return ret;
		}

		private int[] GetMarriageDefinedMatrix(int a0Value, int length)
		{
			int[] ret = new int[length];
			for (int i = 0; i < length; i++)
			{
				ret[i] = marriageProps.MarriageProfile[a0Value < 0 ? 0 : 1][i];
			}
			return ret;
		}
	}
}
